package com.google.code.p.narcissus.ui.components;

import java.awt.Color;
import java.awt.Graphics;
import java.awt.Rectangle;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.image.BufferedImage;
import java.io.File;
import java.io.IOException;

import javax.imageio.ImageIO;
import javax.swing.Timer;

import org.apache.log4j.Logger;

import com.google.code.p.narcissus.core.model.ComparisonResult;
import com.google.code.p.narcissus.core.model.ReferenceMetaData;
import com.google.code.p.narcissus.ui.components.utils.ComparisonMode;

/**
 *
 * @author jadoj
 */
public class PictureComparisonPanel extends ImagePanel {

    private static final Logger LOGGER = Logger.getLogger(PictureComparisonPanel.class);
    private Timer timer;
    /**
     * tells whether the panel currently displays the reference image or something else.
     * Some comparison mode use this to know how to update the panel data.
     */
    private boolean isDisplayingReference = false;
    private BufferedImage reference;
    private ReferenceMetaData referenceMetaData;
    private ComparisonResult pictureComparison;
    /**
     * The comparison mode used.
     * <p>
     *  Depending on the comparisonMode, the differences will be displayed in another way
     * </p>
     */
    private ComparisonMode mode = ComparisonMode.differencesZonesDrawn;

    public void setPictureComparison(ComparisonResult pictureComparison) {
        this.pictureComparison = pictureComparison;
    }

    public void setReference(File referenceFile) throws IOException {
        this.reference = ImageIO.read(referenceFile);
    }

    public void setReferenceMetaData(ReferenceMetaData referenceMetaData) {
        this.referenceMetaData = referenceMetaData;
    }

    public void setComparisonMode(ComparisonMode mode) {
        this.mode = mode;
        this.repaint();
    }

    @Override
    public void paintComponent(Graphics g) {
        try {
            g.setColor(Color.GRAY);
            g.fillRect(0, 0, this.getWidth(), this.getHeight());
            if (getImage() != null) {
                resizePanel();
                switch (this.mode) {
                    case xor:
                        paintXOR(g);
                        break;
                    case differenceZonesOnly:
                        paintDifferenceZonesOnly(g);
                        break;
                    case flip:
                        paintFlip(g);
                        break;
                    default:
                        paintDifferenceZones(g);
                        break;
                }
            }
        } catch (Exception anyEx) {
            LOGGER.error("Impossible to paint the Comparison panel!  See stacktrace for more info", anyEx);
        }

    }

    private boolean isAnExclusionZonePixel(int x, int y) {
        return referenceMetaData != null && referenceMetaData.isAnExclusionZonePixel(x, y);
    }

    private void paintDifferenceZones(Graphics g) {
        g.drawImage(getImage(), 0, 0, getScaledValue(getImage().getWidth()), getScaledValue(getImage().getHeight()), this);
        if (this.pictureComparison != null) {
            g.setColor(Color.red);
            for (Rectangle currentDifferenceZone : pictureComparison.getDifferenceZones()) {
                final int x = ((Double) currentDifferenceZone.getX()).intValue();
                final int y = ((Double) currentDifferenceZone.getY()).intValue();
                final int width = ((Double) currentDifferenceZone.getWidth()).intValue();
                final int height = ((Double) currentDifferenceZone.getHeight()).intValue();
                g.drawRect(getScaledValue(x), getScaledValue(y), getScaledValue(width), getScaledValue(height));
            }

            g.setColor(Color.blue);
            for (Rectangle currentfalsePositive : pictureComparison.getFalsePositiveZones()) {
                final int x = ((Double) currentfalsePositive.getX()).intValue();
                final int y = ((Double) currentfalsePositive.getY()).intValue();
                final int width = ((Double) currentfalsePositive.getWidth()).intValue();
                final int height = ((Double) currentfalsePositive.getHeight()).intValue();
                g.drawRect(getScaledValue(x), getScaledValue(y), getScaledValue(width), getScaledValue(height));
            }
        }
    }

    private void paintXOR(Graphics g) {
        if (reference != null) {
            g.setColor(Color.GRAY);
            g.fillRect(0, 0, getScaledValue(this.reference.getWidth()), getScaledValue(this.reference.getHeight()));

            BufferedImage xorImage = getXorImage(this.reference, getImage(), Color.GRAY, false);
            g.drawImage(xorImage, 0, 0, getScaledValue(xorImage.getWidth()), getScaledValue(xorImage.getHeight()), this);
        }
    }

    private void paintDifferenceZonesOnly(Graphics g) {
        if (timer == null) {
            timer = new Timer(1000, new ActionListener() {

                @Override
                public void actionPerformed(ActionEvent e) {
                    timerTicked(e);
                }
            });
            timer.setInitialDelay(0);
            timer.start();
        } else {
            if (isDisplayingReference) {
                if (this.pictureComparison != null) {
                    for (Rectangle currentDifferenceZone : pictureComparison.getDifferenceZones()) {
                        g.drawImage(getImage().getSubimage(currentDifferenceZone.x, currentDifferenceZone.y, currentDifferenceZone.width, currentDifferenceZone.height), getScaledValue(currentDifferenceZone.x), getScaledValue(currentDifferenceZone.y), getScaledValue(currentDifferenceZone.width), getScaledValue(currentDifferenceZone.height), this);
                    }
                }
            } else {
                g.drawImage(reference, 0, 0, getScaledValue(reference.getWidth()), getScaledValue(reference.getHeight()), this);
            }
        }
    }

    private void paintFlip(Graphics g) {
        if (timer == null) {
            timer = new Timer(1000, new ActionListener() {

                @Override
                public void actionPerformed(ActionEvent e) {
                    timerTicked(e);
                }
            });
            timer.setInitialDelay(0);
            timer.start();
        } else {
            if (isDisplayingReference) {
                g.drawImage(getImage(), 0, 0, getScaledValue(getImage().getWidth()), getScaledValue(getImage().getHeight()), this);
            } else {
                g.drawImage(reference, 0, 0, getScaledValue(reference.getWidth()), getScaledValue(reference.getHeight()), this);
            }
        }
    }

    /**
     * 
     * @param reference
     * @param screenshot
     * @param backgroundColor the color to use for all the pixels that are not different from reference ones;
     * @param useScreenshotColor if true, the color of the pixels added to the image returned will be the one of the pixels in difference, red if not.
     * @return
     */
    private BufferedImage getXorImage(BufferedImage reference, BufferedImage screenshot, Color backgroundColor, boolean useScreenshotColor) {
        BufferedImage xorImage = new BufferedImage(reference.getWidth(), reference.getHeight(), reference.getType());
        for (int y = 0; y < reference.getHeight(); y++) {
            for (int x = 0; x < reference.getWidth(); x++) {
                if (reference.getRGB(x, y) != screenshot.getRGB(x, y)) {
                    if (!isAnExclusionZonePixel(x, y) && !isAFalsePositivePixel(x, y)) {
                        if (useScreenshotColor) {
                            xorImage.setRGB(x, y, screenshot.getRGB(x, y));
                        } else {
                            xorImage.setRGB(x, y, Color.RED.getRGB());
                        }
                    } else {
                        xorImage.setRGB(x, y, backgroundColor.getRGB());
                    }
                } else {
                    xorImage.setRGB(x, y, backgroundColor.getRGB());
                }
            }
        }

        return xorImage;
    }

    private boolean isAFalsePositivePixel(int x, int y) {
        return pictureComparison != null && pictureComparison.isAFalsePositivePixel(x, y);
    }

    void timerTicked(ActionEvent e) {
        isDisplayingReference = !isDisplayingReference;
        repaint();
    }

    public void hidden() {
        if (this.timer != null) {
            this.timer.stop();
        }
    }

    public void displayed() {
        if (this.timer != null) {
            this.timer.start();
        }
    }
}
